﻿/* 
The MIT License (MIT)

Copyright (c) 2013 Tilo Szepan, Immo Wache

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, 
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/
using System;
using System.Reflection;
using System.Collections;
using System.Runtime.CompilerServices;

namespace WPDialog
{
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
    public class EntryAttribute : Attribute{}
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
    public class PasswordAttribute : Attribute{}
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
    public class ActionAttribute : Attribute
    {
        public string Method;
        public ActionAttribute(string method) { Method = method; }
    }
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
    public class TitleAttribute : Attribute
    {
        public TitleAttribute(string title)
        {
            Title = title;
        }
        public string Title;
    }
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
    public class FooterAttribute : Attribute
    {
        public string Footer;
        public FooterAttribute(string footer)
        {
            Footer = footer;
        }
    }
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
    public class HeaderAttribute : Attribute
    {
        public string Header;
        public HeaderAttribute(string header)
        {
            Header = header;
        }
    }
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
    public class PageAttribute : Attribute
    {
        public string Title;
        public PageAttribute(string title)
        {
            Title = title;
        }
    }
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
    public class SeparatorAttribute : Attribute { }
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
    public class DateAttribute : Attribute { }
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
    public class TimeAttribute : Attribute { }
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
    public class CheckboxAttribute : Attribute { }
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
    public class SwitchAttribute : Attribute { }
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
    public class HtmlAttribute : Attribute { }
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
    public class LinkAttribute : Attribute { }
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
    public class SkipAttribute : Attribute { }
    /*    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
        public class AlignmentAttribute : Attribute
        {
            public AlignmentAttribute(TextAlignment alignment)
            {
                Alignment = alignment;
            }
            public TextAlignment Alignment;
        }*/
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]
    public class RadioSelectionAttribute : Attribute { }

    public class BindingContext
    {
        public RootElement Root;

        static object GetValue(MemberInfo mi, object o)
        {
            var fi = mi as FieldInfo;
            if (fi != null)
                return fi.GetValue(o);
            var pi = mi as PropertyInfo;
            if (pi != null)
            {
                var getMethod = pi.GetGetMethod();
                return getMethod.Invoke(o, new object[0]);
            }
            return null;
        }

        static void SetValue(MemberInfo mi, object o, object val)
        {
            var fi = mi as FieldInfo;
            if (fi != null)
            {
                fi.SetValue(o, val);
                return;
            }
            var pi = mi as PropertyInfo;
            if (pi != null)
            {
                var setMethod = pi.GetSetMethod();
                setMethod.Invoke(o, new object[] { val });
            }
        }

        // Returns the type for fields and properties and null for everything else
        static Type GetTypeForMember(MemberInfo mi)
        {
            if (mi is FieldInfo)
                return ((FieldInfo)mi).FieldType;
            else if (mi is PropertyInfo)
                return ((PropertyInfo)mi).PropertyType;
            return null;
        }

        public BindingContext(object o, string title)
        {
            if (o == null)
                throw new ArgumentNullException("o");
            Root = new RootElement(title);
            Populate(o, Root);
        }

        void Populate(object o, RootElement root)
        {
            MemberInfo radio_index = null;
            var members = o.GetType().GetMembers(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
//            BindingFlags.NonPublic | BindingFlags.Instance);

            foreach (var mi in members)
            {
                Type mType = GetTypeForMember(mi);

                if (mType == null)
                    continue;

                if (Attribute.IsDefined(mi, typeof(SkipAttribute), false) || Attribute.IsDefined(mi, typeof(CompilerGeneratedAttribute), false))
                    continue;

                string title = null;
                object[] attrs = mi.GetCustomAttributes(false);
                FooterAttribute footer = null;
                RootElement newPage = null;
                foreach (var attr in attrs)
                {
                    if (attr is TitleAttribute)
                        title = ((TitleAttribute)attr).Title;
                    else if (attr is HeaderAttribute)
                        root.Add(new SeparatorElement((attr as HeaderAttribute).Header, SeparatorStyle.Header));
                    else if(attr is SeparatorAttribute)
                        root.Add(new SeparatorElement());
                    else if(attr is FooterAttribute)
                        footer = attr as FooterAttribute;
                    else if (attr is PageAttribute)
                        newPage = new RootElement((attr as PageAttribute).Title);
                }
                if (newPage != null)
                {
                    root.Add(newPage);
                    root = newPage;
                }

                if (mType == typeof(string))
                {
                    if (title == null)
                        title = mi.Name.Replace('_', ' '); 
                    root.Add(StringElement(mi, attrs, o, title));
                }
                else if (mType == typeof(double))
                {
                    root.Add( FloatElement(mi, attrs, o, title) );
                }
                else if (mType == typeof(bool))
                {
                    if (title == null)
                        title = mi.Name.Replace('_', ' '); 
                    root.Add(BoolElement(mi, attrs, o, title));
                }
                else if (mType == typeof(DateTime))
                {
                    root.Add(DateTimeElement(mi, attrs, o, title));
                }
                else if (mType.IsEnum)
                {
                    root.Add(EnumGroup(mi, attrs, o, mType));
                }
                else if (typeof(IEnumerable).IsAssignableFrom(mType))
                {
                    root.Add( IListGroup(mi, attrs, o, radio_index) );
                    radio_index = null;
                }
                else if (typeof(int) == mType)
                {
                    foreach (object attr in attrs)
                    {
                        if (attr is RadioSelectionAttribute)
                        {
                            radio_index = mi;
                            break;
                        }
                    }
                }
                else
                {
                    var nested = GetValue(mi, o);
                    if (nested != null)
                    {
                        if (title == null)
                            title = mi.Name.Replace('_', ' ');
                        var newRoot = new RootElement(title);
                        Populate(nested, newRoot);
                        root.Add(newRoot);
                    }
                }

                if (newPage != null)
                    root = newPage.Parent;

                if(footer != null)
                    root.Add(new SeparatorElement(footer.Footer, SeparatorStyle.Footer));
            }
        }
        Element StringElement(MemberInfo mi, object[] attrs, object o, string title)
        {
 
            Element element = null;
//            AlignmentAttribute align = null;
            string value = (string)GetValue(mi, o);
            string invoke = null;

            foreach (object attr in attrs)
            {
                if (attr is PasswordAttribute)
                    element = new EntryElement(title, value, true);
                else if (attr is EntryAttribute)
                    element = new EntryElement(title, value);
                else if (attr is HtmlAttribute)
                    element = new HtmlElement(value != null ? value : title);
                else if(attr is LinkAttribute)
                    element = new LinkElement(value != null ? value : title);
                //else if (attr is AlignmentAttribute)
                //    align = attr as AlignmentAttribute;
                else if (attr is ActionAttribute)
                    invoke = (attr as ActionAttribute).Method;
            }
            if (element == null)
            {
                element = new Element(value != null ? value : title);

                if (invoke != null)
                {
                    var method = o.GetType().GetMethod(invoke);
                    if (method == null)
                        throw new Exception("Did not find method " + invoke);
                    element.Action = (e) => { method.Invoke(method.IsStatic ? null : o, new object[] { e }); };
                }
            }
            else if (element is EntryElement)
            {
                element.Action = (e) => { SetValue(mi, o, (e as EntryElement).Value); };
            }

            return element;
        }
        Element BoolElement(MemberInfo mi, object[] attrs, object o, string title)
        {
            bool checkbox = false;
            bool swit = false;
            foreach (object attr in attrs)
            {
                if (attr is CheckboxAttribute)
                    checkbox = true;
                else if (attr is SwitchAttribute)
                    swit = true;
            }

            bool val = (bool)GetValue(mi, o);
            Element element;
            if (checkbox)
            {
                element = new CheckElement(title, val);
                element.Action = (e) => { SetValue(mi, o, (e as CheckElement).Checked); };
            }
            else if (swit)
            {
                element = new SwitchElement(title, val);
                element.Action = (e) => { SetValue(mi, o, (e as SwitchElement).Value); };
            }
            else
            {
                element = new RadioElement(title) { Checked = val };
                element.Action = (e) => { SetValue(mi, o, (e as RadioElement).Checked); };
            }
            return element;
        }
        Element FloatElement(MemberInfo mi, object[] attrs, object o, string title) {
            double val = (double)GetValue(mi, o);
            SliderElement se = title == null ? new SliderElement(true, val) : new SliderElement(title, val);
            se.Action = (e) => { SetValue(mi, o, se.Value); };
            return se;
        }
        Element DateTimeElement(MemberInfo mi, object[] attrs, object o, string title)
        {
            var dateTime = (DateTime)GetValue(mi, o);
            bool asDate = Attribute.IsDefined(mi, typeof(DateAttribute), false);//, asTime = false;

            Element element;
            if (asDate)
            {
                element = new DateElement(dateTime);
                element.Action = (e) => { SetValue(mi, o, (e as DateElement).Value); };
            }
            else // if (asTime)
            {
                element = new TimeElement(dateTime);
                element.Action = (e) => { SetValue(mi, o, (e as TimeElement).Value); };
            }
            if (title != null) element.Title = title;
            return element;
        }
        RadioGroup IListGroup(MemberInfo mi, object[] attrs, object o, MemberInfo radio_index)
        {
            int selected = radio_index != null ? (int)GetValue(radio_index, o) : -1;
            var group = new RadioGroup(selected);

            foreach (var e in (IEnumerable)GetValue(mi, o))
                group.Add(e.ToString());

            if (radio_index != null)
                group.SelectionChanged = (e) => { SetValue(radio_index, o, group.Selected); };

            return group;
        }
        RadioGroup EnumGroup(MemberInfo mi, object[] attrs, object o, Type mType)
        {
            var group = new RadioGroup();
            ulong evalue = Convert.ToUInt64(GetValue(mi, o), null);
            int idx = 0;

            var fields = mType.GetFields(BindingFlags.Public | BindingFlags.Static);
            foreach (var fi in fields)
            {
                ulong v = Convert.ToUInt64(GetValue(fi, null));

                if (v == evalue)
                    group.Selected = idx;

                TitleAttribute ca = Attribute.GetCustomAttribute(fi, typeof(TitleAttribute)) as TitleAttribute;
                group.Add(ca != null ? ca.Title : fi.Name);
                idx++;
            }
            group.SelectionChanged = (e) => { SetValue(mi, o, GetValue(fields[group.Selected], null)); };
            return group;
        }
    }
}
